1   /*
2    * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package com.sun.security.sasl.digest;
27  
28  import java.security.AccessController;
29  import java.security.MessageDigest;
30  import java.security.NoSuchAlgorithmException;
31  import java.io.ByteArrayOutputStream;
32  import java.io.ByteArrayInputStream;
33  import java.io.IOException;
34  import java.io.UnsupportedEncodingException;
35  import java.util.StringTokenizer;
36  import java.util.ArrayList;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Set;
40  import java.util.Arrays;
41  
42  import java.util.logging.Logger;
43  import java.util.logging.Level;
44  
45  import javax.security.sasl.*;
46  import javax.security.auth.callback.CallbackHandler;
47  import javax.security.auth.callback.PasswordCallback;
48  import javax.security.auth.callback.NameCallback;
49  import javax.security.auth.callback.Callback;
50  import javax.security.auth.callback.UnsupportedCallbackException;
51  
52  /**
53    * An implementation of the DIGEST-MD5
54    * (<a href="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</a>) SASL
55    * (<a href="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</a>) mechanism.
56    *
57    * The DIGEST-MD5 SASL mechanism specifies two modes of authentication.
58    * - Initial Authentication
59    * - Subsequent Authentication - optional, (currently unsupported)
60    *
61    * Required callbacks:
62    * - RealmChoiceCallback
63    *    shows user list of realms server has offered; handler must choose one
64    *    from list
65    * - RealmCallback
66    *    shows user the only realm server has offered or none; handler must
67    *    enter realm to use
68    * - NameCallback
69    *    handler must enter username to use for authentication
70    * - PasswordCallback
71    *    handler must enter password for username to use for authentication
72    *
73    * Environment properties that affect behavior of implementation:
74    *
75    * javax.security.sasl.qop
76    *    quality of protection; list of auth, auth-int, auth-conf; default is "auth"
77    * javax.security.sasl.strength
78    *    auth-conf strength; list of high, medium, low; default is highest
79    *    available on platform ["high,medium,low"].
80    *    high means des3 or rc4 (128); medium des or rc4-56; low is rc4-40;
81    *    choice of cipher depends on its availablility on platform
82    * javax.security.sasl.maxbuf
83    *    max receive buffer size; default is 65536
84    * javax.security.sasl.sendmaxbuffer
85    *    max send buffer size; default is 65536; (min with server max recv size)
86    *
87    * com.sun.security.sasl.digest.cipher
88    *    name a specific cipher to use; setting must be compatible with the
89    *    setting of the javax.security.sasl.strength property.
90    *
91    * @see <a href="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</a>
92    * - Simple Authentication and Security Layer (SASL)
93    * @see <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</a>
94    * - Using Digest Authentication as a SASL Mechanism
95    * @see <a href="http://java.sun.com/products/jce">Java(TM)
96    * Cryptography Extension 1.2.1 (JCE)</a>
97    * @see <a href="http://java.sun.com/products/jaas">Java(TM)
98    * Authentication and Authorization Service (JAAS)</a>
99    *
100   * @author Jonathan Bruce
101   * @author Rosanna Lee
102   */
103 final class DigestMD5Client extends DigestMD5Base implements SaslClient {
104     private static final String MY_CLASS_NAME = DigestMD5Client.class.getName();
105 
106     // Property for specifying cipher explicitly
107     private static final String CIPHER_PROPERTY =
108         "com.sun.security.sasl.digest.cipher";
109 
110     /* Directives encountered in challenges sent by the server. */
111     private static final String[] DIRECTIVE_KEY = {
112         "realm",      // >= 0 times
113         "qop",        // atmost once; default is "auth"
114         "algorithm",  // exactly once
115         "nonce",      // exactly once
116         "maxbuf",     // atmost once; default is 65536
117         "charset",    // atmost once; default is ISO 8859-1
118         "cipher",     // exactly once if qop is "auth-conf"
119         "rspauth",    // exactly once in 2nd challenge
120         "stale",      // atmost once for in subsequent auth (not supported)
121     };
122 
123     /* Indices into DIRECTIVE_KEY */
124     private static final int REALM = 0;
125     private static final int QOP = 1;
126     private static final int ALGORITHM = 2;
127     private static final int NONCE = 3;
128     private static final int MAXBUF = 4;
129     private static final int CHARSET = 5;
130     private static final int CIPHER = 6;
131     private static final int RESPONSE_AUTH = 7;
132     private static final int STALE = 8;
133 
134     private int nonceCount; // number of times nonce has been used/seen
135 
136     /* User-supplied/generated information */
137     private String specifiedCipher;  // cipher explicitly requested by user
138     private byte[] cnonce;        // client generated nonce
139     private String username;
140     private char[] passwd;
141     private byte[] authzidBytes;  // byte repr of authzid
142 
143     /**
144       * Constructor for DIGEST-MD5 mechanism.
145       *
146       * @param authzid A non-null String representing the principal
147       * for which authorization is being granted..
148       * @param digestURI A non-null String representing detailing the
149       * combined protocol and host being used for authentication.
150       * @param props The possibly null properties to be used by the SASL
151       * mechanism to configure the authentication exchange.
152       * @param cbh The non-null CallbackHanlder object for callbacks
153       * @throws SaslException if no authentication ID or password is supplied
154       */
155     DigestMD5Client(String authzid, String protocol, String serverName,
156         Map props, CallbackHandler cbh) throws SaslException {
157 
158         super(props, MY_CLASS_NAME, 2, protocol + "/" + serverName, cbh);
159 
160         // authzID can only be encoded in UTF8 - RFC 2222
161         if (authzid != null) {
162             this.authzid = authzid;
163             try {
164                 authzidBytes = authzid.getBytes("UTF8");
165 
166             } catch (UnsupportedEncodingException e) {
167                 throw new SaslException(
168                     "DIGEST-MD5: Error encoding authzid value into UTF-8", e);
169             }
170         }
171 
172         if (props != null) {
173             specifiedCipher = (String)props.get(CIPHER_PROPERTY);
174 
175             logger.log(Level.FINE, "DIGEST60:Explicitly specified cipher: {0}",
176                 specifiedCipher);
177         }
178    }
179 
180     /**
181      * DIGEST-MD5 has no initial response
182      *
183      * @return false
184      */
185     public boolean hasInitialResponse() {
186         return false;
187     }
188 
189     /**
190      * Process the challenge data.
191      *
192      * The server sends a digest-challenge which the client must reply to
193      * in a digest-response. When the authentication is complete, the
194      * completed field is set to true.
195      *
196      * @param challengeData A non-null byte array containing the challenge
197      * data from the server.
198      * @return A possibly null byte array containing the response to
199      * be sent to the server.
200      *
201      * @throws SaslException If the platform does not have MD5 digest support
202      * or if the server sends an invalid challenge.
203      */
204     public byte[] evaluateChallenge(byte[] challengeData) throws SaslException {
205 
206         if (challengeData.length > MAX_CHALLENGE_LENGTH) {
207             throw new SaslException(
208                 "DIGEST-MD5: Invalid digest-challenge length. Got:  " +
209                 challengeData.length + " Expected < " + MAX_CHALLENGE_LENGTH);
210         }
211 
212         /* Extract and process digest-challenge */
213         byte[][] challengeVal;
214 
215         switch (step) {
216         case 2:
217             /* Process server's first challenge (from Step 1) */
218             /* Get realm, qop, maxbuf, charset, algorithm, cipher, nonce
219                directives */
220             List<byte[]> realmChoices = new ArrayList<byte[]>(3);
221             challengeVal = parseDirectives(challengeData, DIRECTIVE_KEY,
222                 realmChoices, REALM);
223 
224             try {
225                 processChallenge(challengeVal, realmChoices);
226                 checkQopSupport(challengeVal[QOP], challengeVal[CIPHER]);
227                 ++step;
228                 return generateClientResponse(challengeVal[CHARSET]);
229             } catch (SaslException e) {
230                 step = 0;
231                 clearPassword();
232                 throw e; // rethrow
233             } catch (IOException e) {
234                 step = 0;
235                 clearPassword();
236                 throw new SaslException("DIGEST-MD5: Error generating " +
237                     "digest response-value", e);
238             }
239 
240         case 3:
241             try {
242                 /* Process server's step 3 (server response to digest response) */
243                 /* Get rspauth directive */
244                 challengeVal = parseDirectives(challengeData, DIRECTIVE_KEY,
245                     null, REALM);
246                 validateResponseValue(challengeVal[RESPONSE_AUTH]);
247 
248 
249                 /* Initialize SecurityCtx implementation */
250                 if (integrity && privacy) {
251                     secCtx = new DigestPrivacy(true /* client */);
252                 } else if (integrity) {
253                     secCtx = new DigestIntegrity(true /* client */);
254                 }
255 
256                 return null; // Mechanism has completed.
257             } finally {
258                 clearPassword();
259                 step = 0;  // Set to invalid state
260                 completed = true;
261             }
262 
263         default:
264             // No other possible state
265             throw new SaslException("DIGEST-MD5: Client at illegal state");
266         }
267     }
268 
269 
270    /**
271     * Record information from the challengeVal array into variables/fields.
272     * Check directive values that are multi-valued and ensure that mandatory
273     * directives not missing from the digest-challenge.
274     *
275     * @throws SaslException if a sasl is a the mechanism cannot
276     * correcly handle a callbacks or if a violation in the
277     * digest challenge format is detected.
278     */
279     private void processChallenge(byte[][] challengeVal, List<byte[]> realmChoices)
280         throws SaslException, UnsupportedEncodingException {
281 
282         /* CHARSET: optional atmost once */
283         if (challengeVal[CHARSET] != null) {
284             if (!"utf-8".equals(new String(challengeVal[CHARSET], encoding))) {
285                 throw new SaslException("DIGEST-MD5: digest-challenge format " +
286                     "violation. Unrecognised charset value: " +
287                     new String(challengeVal[CHARSET]));
288             } else {
289                 encoding = "UTF8";
290                 useUTF8 = true;
291             }
292         }
293 
294         /* ALGORITHM: required exactly once */
295         if (challengeVal[ALGORITHM] == null) {
296             throw new SaslException("DIGEST-MD5: Digest-challenge format " +
297                 "violation: algorithm directive missing");
298         } else if (!"md5-sess".equals(new String(challengeVal[ALGORITHM], encoding))) {
299             throw new SaslException("DIGEST-MD5: Digest-challenge format " +
300                 "violation. Invalid value for 'algorithm' directive: " +
301                 challengeVal[ALGORITHM]);
302         }
303 
304         /* NONCE: required exactly once */
305         if (challengeVal[NONCE] == null) {
306             throw new SaslException("DIGEST-MD5: Digest-challenge format " +
307                 "violation: nonce directive missing");
308         } else {
309             nonce = challengeVal[NONCE];
310         }
311 
312         try {
313             /* REALM: optional, if multiple, stored in realmChoices */
314             String[] realmTokens = null;
315 
316             if (challengeVal[REALM] != null) {
317                 if (realmChoices == null || realmChoices.size() <= 1) {
318                     // Only one realm specified
319                     negotiatedRealm = new String(challengeVal[REALM], encoding);
320                 } else {
321                     realmTokens = new String[realmChoices.size()];
322                     for (int i = 0; i < realmTokens.length; i++) {
323                         realmTokens[i] =
324                             new String(realmChoices.get(i), encoding);
325                     }
326                 }
327             }
328 
329             NameCallback ncb = authzid == null ?
330                 new NameCallback("DIGEST-MD5 authentication ID: ") :
331                 new NameCallback("DIGEST-MD5 authentication ID: ", authzid);
332             PasswordCallback pcb =
333                 new PasswordCallback("DIGEST-MD5 password: ", false);
334 
335             if (realmTokens == null) {
336                 // Server specified <= 1 realm
337                 // If 0, RFC 2831: the client SHOULD solicit a realm from the user.
338                 RealmCallback tcb =
339                     (negotiatedRealm == null? new RealmCallback("DIGEST-MD5 realm: ") :
340                         new RealmCallback("DIGEST-MD5 realm: ", negotiatedRealm));
341 
342                 cbh.handle(new Callback[] {tcb, ncb, pcb});
343 
344                 /* Acquire realm from RealmCallback */
345                 negotiatedRealm = tcb.getText();
346                 if (negotiatedRealm == null) {
347                     negotiatedRealm = "";
348                 }
349             } else {
350                 RealmChoiceCallback ccb = new RealmChoiceCallback(
351                     "DIGEST-MD5 realm: ",
352                     realmTokens,
353                     0, false);
354                 cbh.handle(new Callback[] {ccb, ncb, pcb});
355 
356                 /* Acquire realm from RealmChoiceCallback*/
357                 negotiatedRealm = realmTokens[ccb.getSelectedIndexes()[0]];
358             }
359 
360             passwd = pcb.getPassword();
361             pcb.clearPassword();
362             username = ncb.getName();
363 
364         } catch (UnsupportedCallbackException e) {
365             throw new SaslException("DIGEST-MD5: Cannot perform callback to " +
366                 "acquire realm, authentication ID or password", e);
367 
368         } catch (IOException e) {
369             throw new SaslException(
370                 "DIGEST-MD5: Error acquiring realm, authentication ID or password", e);
371         }
372 
373         if (username == null || passwd == null) {
374             throw new SaslException(
375                 "DIGEST-MD5: authentication ID and password must be specified");
376         }
377 
378         /* MAXBUF: optional atmost once */
379         int srvMaxBufSize =
380             (challengeVal[MAXBUF] == null) ? DEFAULT_MAXBUF
381             : Integer.parseInt(new String(challengeVal[MAXBUF], encoding));
382         sendMaxBufSize =
383             (sendMaxBufSize == 0) ? srvMaxBufSize
384             : Math.min(sendMaxBufSize, srvMaxBufSize);
385     }
386 
387     /**
388      * Parses the 'qop' directive. If 'auth-conf' is specified by
389      * the client and offered as a QOP option by the server, then a check
390      * is client-side supported ciphers is performed.
391      *
392      * @throws IOException
393      */
394     private void checkQopSupport(byte[] qopInChallenge, byte[] ciphersInChallenge)
395         throws IOException {
396 
397         /* QOP: optional; if multiple, merged earlier */
398         String qopOptions;
399 
400         if (qopInChallenge == null) {
401             qopOptions = "auth";
402         } else {
403             qopOptions = new String(qopInChallenge, encoding);
404         }
405 
406         // process
407         String[] serverQopTokens = new String[3];
408         byte[] serverQop = parseQop(qopOptions, serverQopTokens,
409             true /* ignore unrecognized tokens */);
410         byte serverAllQop = combineMasks(serverQop);
411 
412         switch (findPreferredMask(serverAllQop, qop)) {
413         case 0:
414             throw new SaslException("DIGEST-MD5: No common protection " +
415                 "layer between client and server");
416 
417         case NO_PROTECTION:
418             negotiatedQop = "auth";
419             // buffer sizes not applicable
420             break;
421 
422         case INTEGRITY_ONLY_PROTECTION:
423             negotiatedQop = "auth-int";
424             integrity = true;
425             rawSendSize = sendMaxBufSize - 16;
426             break;
427 
428         case PRIVACY_PROTECTION:
429             negotiatedQop = "auth-conf";
430             privacy = integrity = true;
431             rawSendSize = sendMaxBufSize - 26;
432             checkStrengthSupport(ciphersInChallenge);
433             break;
434         }
435 
436         if (logger.isLoggable(Level.FINE)) {
437             logger.log(Level.FINE, "DIGEST61:Raw send size: {0}",
438                 new Integer(rawSendSize));
439         }
440      }
441 
442     /**
443      * Processes the 'cipher' digest-challenge directive. This allows the
444      * mechanism to check for client-side support against the list of
445      * supported ciphers send by the server. If no match is found,
446      * the mechanism aborts.
447      *
448      * @throws SaslException If an error is encountered in processing
449      * the cipher digest-challenge directive or if no client-side
450      * support is found.
451      */
452     private void checkStrengthSupport(byte[] ciphersInChallenge)
453         throws IOException {
454 
455         /* CIPHER: required exactly once if qop=auth-conf */
456         if (ciphersInChallenge == null) {
457             throw new SaslException("DIGEST-MD5: server did not specify " +
458                 "cipher to use for 'auth-conf'");
459         }
460 
461         // First determine ciphers that server supports
462         String cipherOptions = new String(ciphersInChallenge, encoding);
463         StringTokenizer parser = new StringTokenizer(cipherOptions, ", \t\n");
464         int tokenCount = parser.countTokens();
465         String token = null;
466         byte[] serverCiphers = { UNSET,
467                                  UNSET,
468                                  UNSET,
469                                  UNSET,
470                                  UNSET };
471         String[] serverCipherStrs = new String[serverCiphers.length];
472 
473         // Parse ciphers in challenge; mark each that server supports
474         for (int i = 0; i < tokenCount; i++) {
475             token = parser.nextToken();
476             for (int j = 0; j < CIPHER_TOKENS.length; j++) {
477                 if (token.equals(CIPHER_TOKENS[j])) {
478                     serverCiphers[j] |= CIPHER_MASKS[j];
479                     serverCipherStrs[j] = token; // keep for replay to server
480                     logger.log(Level.FINE, "DIGEST62:Server supports {0}", token);
481                 }
482             }
483         }
484 
485         // Determine which ciphers are available on client
486         byte[] clntCiphers = getPlatformCiphers();
487 
488         // Take intersection of server and client supported ciphers
489         byte inter = 0;
490         for (int i = 0; i < serverCiphers.length; i++) {
491             serverCiphers[i] &= clntCiphers[i];
492             inter |= serverCiphers[i];
493         }
494 
495         if (inter == UNSET) {
496             throw new SaslException(
497                 "DIGEST-MD5: Client supports none of these cipher suites: " +
498                 cipherOptions);
499         }
500 
501         // now have a clear picture of user / client; client / server
502         // cipher options. Leverage strength array against what is
503         // supported to choose a cipher.
504         negotiatedCipher = findCipherAndStrength(serverCiphers, serverCipherStrs);
505 
506         if (negotiatedCipher == null) {
507             throw new SaslException("DIGEST-MD5: Unable to negotiate " +
508                 "a strength level for 'auth-conf'");
509         }
510         logger.log(Level.FINE, "DIGEST63:Cipher suite: {0}", negotiatedCipher);
511     }
512 
513     /**
514      * Steps through the ordered 'strength' array, and compares it with
515      * the 'supportedCiphers' array. The cipher returned represents
516      * the best possible cipher based on the strength preference and the
517      * available ciphers on both the server and client environments.
518      *
519      * @param tokens The array of cipher tokens sent by server
520      * @return The agreed cipher.
521      */
522     private String findCipherAndStrength(byte[] supportedCiphers,
523         String[] tokens) {
524         byte s;
525         for (int i = 0; i < strength.length; i++) {
526             if ((s=strength[i]) != 0) {
527                 for (int j = 0; j < supportedCiphers.length; j++) {
528 
529                     // If user explicitly requested cipher, then it
530                     // must be the one we choose
531 
532                     if (s == supportedCiphers[j] &&
533                         (specifiedCipher == null ||
534                             specifiedCipher.equals(tokens[j]))) {
535                         switch (s) {
536                         case HIGH_STRENGTH:
537                             negotiatedStrength = "high";
538                             break;
539                         case MEDIUM_STRENGTH:
540                             negotiatedStrength = "medium";
541                             break;
542                         case LOW_STRENGTH:
543                             negotiatedStrength = "low";
544                             break;
545                         }
546 
547                         return tokens[j];
548                     }
549                 }
550             }
551         }
552 
553         return null;  // none found
554     }
555 
556     /**
557      * Returns digest-response suitable for an initial authentication.
558      *
559      * The following are qdstr-val (quoted string values) as per RFC 2831,
560      * which means that any embedded quotes must be escaped.
561      *    realm-value
562      *    nonce-value
563      *    username-value
564      *    cnonce-value
565      *    authzid-value
566      * @returns <tt>digest-response</tt> in a byte array
567      * @throws SaslException if there is an error generating the
568      * response value or the cnonce value.
569      */
570     private byte[] generateClientResponse(byte[] charset) throws IOException {
571 
572         ByteArrayOutputStream digestResp = new ByteArrayOutputStream();
573 
574         if (useUTF8) {
575             digestResp.write("charset=".getBytes(encoding));
576             digestResp.write(charset);
577             digestResp.write(',');
578         }
579 
580         digestResp.write(("username=\"" +
581             quotedStringValue(username) + "\",").getBytes(encoding));
582 
583         if (negotiatedRealm.length() > 0) {
584             digestResp.write(("realm=\"" +
585                 quotedStringValue(negotiatedRealm) + "\",").getBytes(encoding));
586         }
587 
588         digestResp.write("nonce=\"".getBytes(encoding));
589         writeQuotedStringValue(digestResp, nonce);
590         digestResp.write('"');
591         digestResp.write(',');
592 
593         nonceCount = getNonceCount(nonce);
594         digestResp.write(("nc=" +
595             nonceCountToHex(nonceCount) + ",").getBytes(encoding));
596 
597         cnonce = generateNonce();
598         digestResp.write("cnonce=\"".getBytes(encoding));
599         writeQuotedStringValue(digestResp, cnonce);
600         digestResp.write("\",".getBytes(encoding));
601         digestResp.write(("digest-uri=\"" + digestUri + "\",").getBytes(encoding));
602 
603         digestResp.write("maxbuf=".getBytes(encoding));
604         digestResp.write(String.valueOf(recvMaxBufSize).getBytes(encoding));
605         digestResp.write(',');
606 
607         try {
608             digestResp.write("response=".getBytes(encoding));
609             digestResp.write(generateResponseValue("AUTHENTICATE",
610                 digestUri, negotiatedQop, username,
611                 negotiatedRealm, passwd, nonce, cnonce,
612                 nonceCount, authzidBytes));
613             digestResp.write(',');
614         } catch (Exception e) {
615             throw new SaslException(
616                 "DIGEST-MD5: Error generating response value", e);
617         }
618 
619         digestResp.write(("qop=" + negotiatedQop).getBytes(encoding));
620 
621         if (negotiatedCipher != null) {
622             digestResp.write((",cipher=\"" + negotiatedCipher + "\"").getBytes(encoding));
623         }
624 
625         if (authzidBytes != null) {
626             digestResp.write(",authzid=\"".getBytes(encoding));
627             writeQuotedStringValue(digestResp, authzidBytes);
628             digestResp.write("\"".getBytes(encoding));
629         }
630 
631         if (digestResp.size() > MAX_RESPONSE_LENGTH) {
632             throw new SaslException ("DIGEST-MD5: digest-response size too " +
633                 "large. Length: "  + digestResp.size());
634         }
635         return digestResp.toByteArray();
636      }
637 
638 
639     /**
640      * From RFC 2831, Section 2.1.3: Step Three
641      * [Server] sends a message formatted as follows:
642      *     response-auth = "rspauth" "=" response-value
643      * where response-value is calculated as above, using the values sent in
644      * step two, except that if qop is "auth", then A2 is
645      *
646      *  A2 = { ":", digest-uri-value }
647      *
648      * And if qop is "auth-int" or "auth-conf" then A2 is
649      *
650      *  A2 = { ":", digest-uri-value, ":00000000000000000000000000000000" }
651      */
652     private void validateResponseValue(byte[] fromServer) throws SaslException {
653         if (fromServer == null) {
654             throw new SaslException("DIGEST-MD5: Authenication failed. " +
655                 "Expecting 'rspauth' authentication success message");
656         }
657 
658         try {
659             byte[] expected = generateResponseValue("",
660                 digestUri, negotiatedQop, username, negotiatedRealm,
661                 passwd, nonce, cnonce,  nonceCount, authzidBytes);
662             if (!Arrays.equals(expected, fromServer)) {
663                 /* Server's rspauth value does not match */
664                 throw new SaslException(
665                     "Server's rspauth value does not match what client expects");
666             }
667         } catch (NoSuchAlgorithmException e) {
668             throw new SaslException(
669                 "Problem generating response value for verification", e);
670         } catch (IOException e) {
671             throw new SaslException(
672                 "Problem generating response value for verification", e);
673         }
674     }
675 
676     /**
677      * Returns the number of requests (including current request)
678      * that the client has sent in response to nonceValue.
679      * This is 1 the first time nonceValue is seen.
680      *
681      * We don't cache nonce values seen, and we don't support subsequent
682      * authentication, so the value is always 1.
683      */
684     private static int getNonceCount(byte[] nonceValue) {
685         return 1;
686     }
687 
688     private void clearPassword() {
689         if (passwd != null) {
690             for (int i = 0; i < passwd.length; i++) {
691                 passwd[i] = 0;
692             }
693             passwd = null;
694         }
695     }
696 }